
#define GL3_PROTOTYPES
#include <iostream>
#include <sstream>
#include <GL/glew.h>

#include "DisplayObject.h"
#include "Matrix4.h"
#include "MatrixHelpers.h"
#include "Shader.h"


namespace Krengine
{
	/**
	 * constructor. Registers object's position
	 */
	DisplayObject::DisplayObject(float x, float y, float z)
		: xPos(x), yPos(y), zPos(z),
		  xRot(0.0f), yRot(0.0f), zRot(0.0f),
		  doShader(new Shader("test")), buffersInit(false)
	{
		std::cout << "DisplayObject created" << std::endl;
	}


	/**
	 * empty destructor
	 */
	//TODO perform cleanup
	DisplayObject::~DisplayObject() { }


	/**
	 * translates the object's x position to the given absolute position
	 */
	void DisplayObject::translate( float x, float y, float z )
	{
		/* register new coordinate */
		xPos += x;
		yPos += y;
		zPos += z;
		
		/* update model matrix */
		//modelMatrix = modelMatrix * translateMatrix( x, y, z );

		//updateMatrices();
	}


	/**
	 * rotates the object to the given absolute x-axis rotation
	 */
	void DisplayObject::rotate( float xAngle, float yAngle, float zAngle )
	{
		/* register new rotation */
		xRot += xAngle;
		yRot += yAngle;
		zRot += zAngle;

		/* update model matrix */
		/*if ( xAngle != 0.0f )
			modelMatrix = modelMatrix * rotateLocalXMatrix( xAngle );

		if ( yAngle != 0.0f )
			modelMatrix = modelMatrix * rotateLocalYMatrix( yAngle );

		if ( zAngle != 0.0f )
			modelMatrix = modelMatrix * rotateLocalZMatrix( zAngle );*/

		//updateMatrices();
	}


	/**
	 * changes the DO's shader to the requested type
	 */
	bool DisplayObject::setShader( int _shaderType )
	{
		//TODO FIXME
		std::cout << "FIXME: DisplayObject::setShader()" << std::endl;

		return false;


		/* load requested shader */
		//Shader *temp = ShaderList::getShader( _shaderType );
		/*if ( !temp->init() )
		{
			std::cout << "Error: shader initialization failed.\n";
			delete temp;
			return false;
		}*/

		/* delete current shader */
		doShader->cleanup();
		delete doShader;

		/* register reference to newly created shader */
		//doShader = temp;

		/* setup uniforms for the new shader */
		glBindVertexArray(vaoID);
		doShader->setupUniforms();

		return true;
	}


	/**
	 * generates and fills a Vertex Array Object to be used when rendering
	 */
	//TODO check if everything is initialized, to prevent any possible errors
	bool DisplayObject::prepareForRender( Matrix4 *projectionMatrix )
	{
		/* save reference to the environment's projection matrix */
		projMatrix = projectionMatrix;

		/* load default shader */
		if ( !doShader->init() )
		{
			std::cout << "Error: shader initialization failed." << std::endl;
			return false;
		}

		/* create and bind VertexArrayOject */
		glGenVertexArrays(1, &vaoID);
		glBindVertexArray(vaoID);

		/* generate buffer names */
		glGenBuffers(numBuf, bufferNm);

		/* vertex buffer */
		glBindBuffer(GL_ARRAY_BUFFER, bufferNm[vertices]);
		glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * bufferSize[vertices],
							vertBf, GL_STATIC_DRAW);
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_TRUE, 0, 0);
		glEnableVertexAttribArray((GLuint)0);

		/* color buffer */
		glBindBuffer(GL_ARRAY_BUFFER, bufferNm[color]);
		glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * bufferSize[color],
							colorBf, GL_STATIC_DRAW);
		glVertexAttribPointer(1, 3, GL_FLOAT, GL_TRUE, 0, 0);
		glEnableVertexAttribArray((GLuint)1);

		/* normal buffer */
		glBindBuffer(GL_ARRAY_BUFFER, bufferNm[normal]);
		glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * bufferSize[normal],
							normalBf, GL_STATIC_DRAW);
		glVertexAttribPointer(2, 3, GL_FLOAT, GL_TRUE, 0, 0);
		glEnableVertexAttribArray((GLuint)2);

		/* index buffer */
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferNm[index]);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * bufferSize[index],
							indexBf, GL_STATIC_DRAW);

		doShader->setupUniforms();

		/* setup matrices */
		updateMatrices();

		/* all buffers and shaders initialized */
		buffersInit = true;
		return buffersInit;
	}


	/**
	 * deallocates any used buffers and other resources
	 */
	void DisplayObject::cleanupShader()
	{
		if ( buffersInit )
		{
			/* cleanup resources */
			doShader->cleanup();
			glDeleteBuffers(numBuf, bufferNm);
			glDeleteVertexArrays(1, &vaoID);
		}
	}


	/**
	 * Draws the object on screen
	 */
	void DisplayObject::render()
	{
		updateMatrices();

		glBindVertexArray(vaoID);
		doShader->sendUniformData( modelMatrix, normalMatrix, mvpMatrix );
		glDrawElements(GL_TRIANGLES, bufferSize[index], GL_UNSIGNED_INT, 0);
	}


	/**
	 * Outputs the objects coordinates
	 */
	const std::string DisplayObject::toString() const
	{
		/* stringstream to build the string */
		std::stringstream ss( std::stringstream::in | std::stringstream::out );

		ss << " -- DisplayObject --" << std::endl
				<< "position (x,y,z): ("
					<< xPos << "," << yPos << "," << zPos << ")" << std::endl
				<< "rotation (x,y,z): ("
					<< xRot << "," << yRot << "," << zRot << ")" << std::endl
			<< " -- End of DisplayObject --" << std::endl;

		return std::string(ss.str());
	}

	/************************* private functions ***************************/
	/**
	 * updates all the object's matrices and sends the new values to the shaderprogram
	 */
	void DisplayObject::updateMatrices()
	{
		modelMatrix = identityMatrix();
		modelMatrix = modelMatrix * translateMatrix( xPos, yPos, zPos );

		//TODO fixen zodat ik gewoon die functies achter elkaar kan zetten...
		Matrix4 temp = rotateLocalXMatrix( xRot );
		modelMatrix = temp * modelMatrix;

		temp = rotateLocalYMatrix( yRot );
		modelMatrix = temp * modelMatrix;

		temp = rotateLocalZMatrix( zRot );
		modelMatrix = temp * modelMatrix;

		/* update normal matrix */
		normalMatrix = modelMatrix;
		/* take upper-left 3x3 matrix of modelMatrix */
		normalMatrix.data[3]  = normalMatrix.data[7]  = normalMatrix.data[11] = 0.0f;
		normalMatrix.data[12] = normalMatrix.data[13] = normalMatrix.data[14] = 0.0f;
		normalMatrix.data[15] = 1.0f;
		normalMatrix.inverse();
		normalMatrix.transpose();

		/* update modelview-projection matrix */
		mvpMatrix = modelMatrix * (*projMatrix);
	}
}
